#pragma once

#define DEBUG_PILO_MEMORY_TCFIXEDRINGBUFFER

#ifdef DEBUG_PILO_MEMORY_TCFIXEDRINGBUFFER
#include "string/stringoutput.h"
#endif

#include "./buffer.h"

namespace PILO 
{
    namespace Core
    {
        namespace Memory 
        {
            namespace Buffer
            {
                template<typename T, size_t bufferCapacity> class TCFixedRingBuffer
                {
                    protected: //variables
                        size_t  _m_dataBasePosition;
                        size_t  _m_dataLength;
                        size_t  _m_capacity;
                        T   _m_buffer[bufferCapacity];
                        T*  _m_bufferPtr;

                    public: //methods 
                        TCFixedRingBuffer()
                        {
                            if (bufferCapacity > MC_LIMIT_TCFIXEDBUFFER_MAX_ELEMENT_NUMBER) {
                                throw EC_REACH_UPPER_LIMIT;
                            }
                            if (bufferCapacity * sizeof(T) > MC_LIMIT_TCFIXEDBUFFER_MAX_DATA_SIZE) {
                                throw EC_REACH_UPPER_LIMIT;
                            }

                            _m_dataBasePosition = 0;
                            _m_dataLength = 0;
                            _m_capacity = bufferCapacity;
                            _m_bufferPtr = _m_buffer;
                        }

                        void attach(T* p, size_t capacity, size_t dataPosition, size_t dataLength)
                        {
                            if (capacity > MC_LIMIT_TCFIXEDBUFFER_MAX_ELEMENT_NUMBER) {
                                throw EC_REACH_UPPER_LIMIT;
                                
                            }
                            if (capacity * sizeof(T) > MC_LIMIT_TCFIXEDBUFFER_MAX_DATA_SIZE) {
                                throw EC_REACH_UPPER_LIMIT;
                            }

                            ASSERT(dataPosition + dataLength <= capacity);
                            _m_capacity = capacity;
                            _m_bufferPtr = (T*) p;
                            _m_dataBasePosition = dataPosition;
                            _m_dataLength = dataLength;
                        }

                        size_t elementSize() const
                        {
                            return sizeof(T);
                        }

                        size_t internalBufferLength() const
                        {
                            return _m_capacity;
                        }

                        const T* internalBuffer() const
                        {
                            return &_m_bufferPtr[0];               
                        }

                        const T* dataForReading() const
                        {
                            if (_m_dataLength > 0) {
                                &_m_bufferPtr[_m_dataBasePosition];
                            }
                            return static_cast<T*>(0);       
                        }

                        const T* bufferForWriting() const
                        {
                            size_t pos = indexForWriting();
                            if (MC_INVALID_SIZE == pos) {
                                return static_cast<T*>(0);
                            }
                            return &_m_bufferPtr[pos];
                        }

                        size_t dataSize() const
                        {
                            return _m_dataLength * sizeof(T);
                        }

                        size_t indexForReading() const
                        {
                            if (_m_dataLength > 0) {
                                return _m_dataBasePosition;
                            }
                            return MC_INVALID_SIZE;
                        }

                        size_t indexForWriting() const
                        {
                            if (_m_dataLength < _m_capacity) {
                                uint_r wPosition = _m_dataBasePosition + _m_dataLength;
                                if (wPosition< _m_capacity) {
                                    return wPosition;
                                } else {
                                    return wPosition - _m_capacity;
                                }
                            }
                            return MC_INVALID_SIZE;
                        }

                        size_t readAwailable() const
                        {
                            return _m_dataLength;
                        }

                        size_t writeAwaiable() const
                        {
                            return _m_capacity - _m_dataLength;
                        }

                        size_t read(T* userBuffer, size_t requiredReadLength, DWORD flag=0)
                        {
                            size_t availableCount = this->readAwailable();

                            if (availableCount < requiredReadLength) {
                                if (flag & Buffer::EnumNoopOnPartial) { //need full data at one call
                                    return MC_INVALID_SIZE; //bailing out
                                }
                                else if (availableCount < 1) {
                                    return 0;
                                }
                                requiredReadLength = availableCount;
                            }

                            if (_m_dataBasePosition + requiredReadLength > _m_capacity) { // looped
                                //calculate first part of data
                                size_t firstPartSize = _m_capacity - _m_dataBasePosition;
                                //copy first part of data
                                memcpy(userBuffer, &_m_bufferPtr[_m_dataBasePosition], firstPartSize*sizeof(T));
                                //update index after copy complete
                                _m_dataLength -= firstPartSize;
                                _m_dataBasePosition  = 0;

                                //calculate second part of data
                                size_t secondPartSize = requiredReadLength - firstPartSize;
                                //copy second part of data
                                memcpy(&userBuffer[firstPartSize], &_m_bufferPtr[0], secondPartSize * sizeof(T));
                                _m_dataLength -= secondPartSize;
                                _m_dataBasePosition += secondPartSize;

                                return requiredReadLength;

                            } else { //normal case
                                memcpy(userBuffer, &_m_bufferPtr[_m_dataBasePosition], requiredReadLength*sizeof(T));
                                _m_dataBasePosition += requiredReadLength;
                                _m_dataLength -= requiredReadLength;

                                //force loop from start
                                //ASSERT(_m_dataBasePosition <= _m_capacity)
                                if (_m_dataBasePosition == _m_capacity) {
                                    _m_dataBasePosition  = 0;
                                }                        
                            }
                           
                            return requiredReadLength;
                        }

                        size_t write(const T* userBuffer, size_t requiredWriteLength, DWORD flag=0)
                        {
                            size_t availableCount = this->writeAwaiable();
                            if (availableCount < requiredWriteLength) {
                                if (flag & Buffer::EnumNoopOnPartial) {
                                    return MC_INVALID_SIZE;
                                }
                                else if (availableCount < 1) {
                                    return 0;
                                }
                                requiredWriteLength = availableCount;
                            }

                            size_t pos = this->indexForWriting();
                            ASSERT(MC_INVALID_SIZE != pos);
                            if (pos + requiredWriteLength > _m_capacity) {//looped
                                //calculate first part of data
                                size_t firstPartOfData = _m_capacity - pos;
                                //copy first part of data
                                memcpy(&_m_bufferPtr[pos], userBuffer,firstPartOfData * sizeof(T));
                                //update length value
                                _m_dataLength += firstPartOfData;

                                //calculate second part of data
                                size_t secondPartData = requiredWriteLength - firstPartOfData;
                                //copy second part of data
                                memcpy(&_m_bufferPtr[0], &userBuffer[firstPartOfData], secondPartData * sizeof(T));
                                //update length value
                                _m_dataLength += secondPartData;
                                
                            } else { //normal case
                                memcpy(&_m_bufferPtr[pos], userBuffer, requiredWriteLength * sizeof(T));
                                _m_dataLength += requiredWriteLength;
                            }

                            return requiredWriteLength;
                        }

                        size_t peek(T* userBuffer, size_t requiredReadLength, DWORD flag=0)
                        {
                            size_t  originalPos = _m_dataBasePosition;
                            size_t  originalLen = _m_dataLength;

                            size_t nRead = this->read(userBuffer, requiredReadLength, flag);

                            _m_dataBasePosition = originalPos;
                            _m_dataLength = originalLen;

                            return nRead;
                        }

                        void reset()
                        {
                            _m_dataLength = 0;
                            _m_dataBasePosition = 0;
                        }

                        #ifdef DEBUG_PILO_MEMORY_TCFIXEDRINGBUFFER
                        void debugObjectInformationToConsole()
                        {
                            FormatOutputToConsole(TEXT("\nObject Information:\n"));
                            FormatOutputToConsole(TEXT(" > Type=[TCFixedBuffer]Size=(%u) addr=(0x%p) ElementSize=(%u)\n"), sizeof(*this), this, this->elementSize());
                            FormatOutputToConsole(TEXT(" > BufferBase=(0x%p) BufferLength=(%u:%u)\n"), this->internalBuffer(),this->internalBufferLength(),this->internalBufferLength()*this->elementSize());
                            FormatOutputToConsole(TEXT(" > DataForRead=(0x%p:%u) ReadAvailable=(%u)\n"), this->dataForReading(), this->indexForReading(), this->readAwailable());
                            FormatOutputToConsole(TEXT(" > BufferForWrite=(0x%p:%u) WriteAvailable=(%u)\n"),this->bufferForWriting(), this->indexForWriting(),this->writeAwaiable());                    
                        }
                        #endif

                };
            }

        }
    }
} 

